using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

using Microsoft.Xna.Framework;

using QuickStart.Physics;

using JigLibX.Physics;
using JigLibX.Collision;

namespace QuickStart.Physics.JigLibX
{
    /// <summary>
    /// An implementation of <see cref="IPhysicsScene"/> using JigLibX.
    /// </summary>
    public class JigLibXScene : IPhysicsScene
    {
        private PhysicsSystem jigLibXPhysics;
        private float prevDt = 0.0f;
        private Thread processingThread;
        private QSGame game;

        private ManualResetEvent startFrame;
        private ManualResetEvent endFrame;
        private ManualResetEvent endThread;

        /// <summary>
        /// Gets/sets the gravity vector.
        /// </summary>
        public Vector3 Gravity
        {
            get
            {
                return jigLibXPhysics.Gravity;
            }
            set
            {
                jigLibXPhysics.Gravity = value;
            }
        }

        /// <summary>
        /// Constructs a new physics scene.
        /// </summary>
        /// <param name="game">A reference to the current <see cref="QSGame"/> instance.</param>
        internal JigLibXScene(QSGame game)
        {
            // Create JigLibX scene with standard settings.
            // TODO:  Expose some of these settings in a generic way.
            jigLibXPhysics = new PhysicsSystem();
            jigLibXPhysics.CollisionSystem = new CollisionSystemGrid(32, 32, 4, 13, 13, 13);
            jigLibXPhysics.EnableFreezing = true;
            jigLibXPhysics.SolverType = PhysicsSystem.Solver.Normal;
            jigLibXPhysics.CollisionSystem.UseSweepTests = false;
            jigLibXPhysics.AllowedPenetration = 0.0001f;

            // Create synchronization handles
            startFrame = new ManualResetEvent(false);
            endFrame = new ManualResetEvent(false);
            endThread = new ManualResetEvent(false);

            // Create and start the physics processing thread.
            processingThread = new Thread(SceneProcessing);
            processingThread.Priority = ThreadPriority.Normal;
            processingThread.IsBackground = false;
            processingThread.Start();

            this.game = game;
            // Register message handler.
            this.game.GameMessage += OnGameMessage;
        }

        /// <summary>
        /// Creates a new physics actor.
        /// </summary>
        /// <param name="desc">The actor descriptor.</param>
        /// <returns>The new actor instance.</returns>
        public IPhysicsActor CreateActor(ActorDesc desc)
        {
            return new JigLibXActor(desc);
        }

        /// <summary>
        /// Begins physics processing.
        /// </summary>
        /// <param name="gameTime">The XNA <see cref="GameTime"/> structure for this frame.</param>
        public void BeginFrame(GameTime gameTime)
        {
            // Save the time delta and signal the physics thread to start processing.
            float dt = (float)gameTime.ElapsedGameTime.TotalMilliseconds / 1000.0f;

            prevDt = dt;

            startFrame.Set();
        }

        /// <summary>
        /// Blocks until the current frame's processing is complete.
        /// </summary>
        public void EndFrame()
        {
            // Block until the physics frame is finished processing.
            endFrame.WaitOne(Timeout.Infinite, false);
            endFrame.Reset();
        }

        /// <summary>
        /// Releases all unmanaged resources for the scene.
        /// </summary>
        public void Dispose()
        {
        }

        /// <summary>
        /// The main scene processing thread process.
        /// </summary>
        private void SceneProcessing()
        {
#if XBOX360
            Thread.CurrentThread.SetProcessorAffinity(new int[] {4});
#endif
            try
            {
                // Execute until we are exiting.
                while(!endThread.WaitOne(0, false))
                {
                    // Wait for frame start event.
                    if(!startFrame.WaitOne(10, false))
                    {
                        continue;
                    }

                    // Reset the event.
                    startFrame.Reset();

                    // Process the frame.
                    jigLibXPhysics.Integrate(prevDt);

                    // Signal frame completion.
                    endFrame.Set();
                }
            }
            catch(ThreadAbortException)
            {
            }
        }

        /// <summary>
        /// Handle engine messages.
        /// </summary>
        /// <param name="message">A <see cref="IMessage"/> reference to the message to handle.</param>
        private void OnGameMessage(IMessage message)
        {
            if(message.Type == MessageType.Shutdown)
            {
                endThread.Set();
            }
        }
    }
}
